import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import httpRequestAndResponse.MyHttpRequest;
import httpRequestAndResponse.MyHttpResponse;
import org.yaml.snakeyaml.Yaml;
import servlet.MyServlet;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

public class MyTomcat {

    //用于存放 Java类 全类名
    private static ArrayList<String> javaClasses = new ArrayList<>();

    //用于存放 Servlet 的类对象 , 其中 Key 是 Servlet 的url , value 是 Servlet 的类对象
    private static HashMap<String, Class> servletMap = new HashMap<>();

    //记录连接序号
    private static int connectSerialNumber = 1;

    public static void main(String[] args) throws IOException {

        //创建JUL日志执行器
        Logger myTomcatLogger = Logger.getLogger("MyTomcat");

        //创建线程池
        ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();

        //启动阶段
        startupPhase(myTomcatLogger);

        //注册端口
        //不指定 InetAddress 时，ServerSocket 默认绑定到 0.0.0.0，即监听所有 IP 地址
        ServerSocket serverSocket = new ServerSocket(8080, 10);

        while (true) {

            myTomcatLogger.info("等待建立连接 - " + connectSerialNumber);
            Socket socket = serverSocket.accept();
            myTomcatLogger.info("连接已建立 - " + connectSerialNumber);
            connectSerialNumber++;

            threadPoolExecutor.execute(() -> {

                //接收Http请求报文
                ArrayList<String> requestMessage = receiveHttp(socket, myTomcatLogger);

                //处理请求
                //获取请求行
                String requestLine = requestMessage.get(0);
                //获取请求方式
                String requestStyle = requestLine.split(" ")[0];

                if (requestStyle.equals("GET")) {

                    String requestPathAndParameter = requestLine.split(" ")[1];

                    //创建HttpRequest对象
                    MyHttpRequest myHttpRequest = new MyHttpRequest();

                    //获取请求路径，并将参数置入HttpRequest对象
                    String requestPath = getRequestPathAndSetMyHttpRequest(requestPathAndParameter, myHttpRequest, myTomcatLogger);

                    //还没处理 favicon.ico 请求，先屏蔽
                    if (requestPath.equals("favicon.ico")) {
                        myTomcatLogger.info("暂时无法响应 favicon.ico");
                        return;
                    }

                    //创建HttpResponse对象
                    MyHttpResponse myHttpResponse = getMyHttpResponse(socket);

                    //反射调用Servlet方法
                    servletActuator(requestPath, "doGet", myHttpRequest, myHttpResponse);

                } else {
                    //获取请求路径
                    String requestPath=requestMessage.get(0).split(" ")[1].substring(1);

                    //创建HttpRequest对象
                    MyHttpRequest postHttpRequestget = getPostHttpRequest(requestMessage);

                    //创建HttpResponse对象
                    MyHttpResponse myHttpResponse = getMyHttpResponse(socket);

                    //反射调用Servlet方法
                    servletActuator(requestPath, "doPost", postHttpRequestget, myHttpResponse);
                }
            });
        }
    }

    /**
     * 根据 Post请求 的参数列表创建 HttpRequest 对象
     * @param requestMessage
     * @return
     */
    private static MyHttpRequest getPostHttpRequest(ArrayList<String> requestMessage){
        MyHttpRequest postHttpRequest=new MyHttpRequest();
        StringBuilder paramJSON=getPostParam(requestMessage);
        if (paramJSON!=null){
            HashMap<String, String> tempMap = JSON.parseObject(paramJSON.toString()).toJavaObject(new TypeReference<HashMap<String, String>>() {});
            postHttpRequest.setMap(tempMap);
        }
        return postHttpRequest;
    }

    /**
     * 获取 Post请求 的 JSON形式参数列表
     * @param requestMessage
     * @return
     */
    private static StringBuilder getPostParam(ArrayList<String> requestMessage){
        int requestBodyBegin = requestMessage.indexOf("{");
        if (requestBodyBegin==-1){
            return null;
        }else{
            int requestBodyEnd = requestMessage.indexOf("}");
            StringBuilder paramJSON=new StringBuilder();
            for (int i=requestBodyBegin;i<=requestBodyEnd;i++){
                String paramLine=requestMessage.get(i);
                if (paramLine.equals("{")||paramLine.equals("}")){
                    paramJSON.append(paramLine);
                }else{
                    int spaceBegin=paramLine.indexOf("\"");
                    paramJSON.append(paramLine.substring(spaceBegin));
                }
            }
            return paramJSON;
        }
    }

    /**
     * 通过反射调用Servlet的方法
     * @param requestPath
     * @param methodName
     * @param myHttpRequest
     * @param myHttpResponse
     */
    private static void servletActuator(String requestPath, String methodName, MyHttpRequest myHttpRequest, MyHttpResponse myHttpResponse) {
        try {
            Class<?> servletClass = servletMap.get(requestPath);
            Method method = servletClass.getMethod(methodName, MyHttpRequest.class, MyHttpResponse.class);
            method.invoke(servletClass.newInstance(), myHttpRequest, myHttpResponse);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }

    /**
     * 创建 MyHttpResponse 对象
     * @param socket
     * @return
     */
    private static MyHttpResponse getMyHttpResponse(Socket socket) {
        OutputStream outputStream = null;
        try {
            outputStream = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new MyHttpResponse(outputStream);
    }

    /**
     * 接收Http请求报文
     * @param socket
     * @param logger
     * @return
     */
    private static ArrayList<String> receiveHttp(Socket socket, Logger logger) {
        ArrayList<String> httpMessage = new ArrayList<>();
        try {
            logger.info("开始接收Http");
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String httpMessageLine=reader.readLine();
            System.out.println(httpMessageLine);
            httpMessage.add(httpMessageLine);
            String requestType=httpMessageLine.split(" ")[0];
            if (requestType.equals("GET")){
                while ((httpMessageLine = reader.readLine()).length() != 0) {
                    httpMessage.add(httpMessageLine);
                    System.out.println(httpMessageLine);
                }
            }else {
                while (!httpMessageLine.equals("}")){
                    httpMessageLine=reader.readLine();
                    if (httpMessageLine.startsWith("Content-Length")){
                        int contentLength=Integer.parseInt(httpMessageLine.split(" ")[1]);
                        if (contentLength==0){
                            httpMessage.add(httpMessageLine);
                            System.out.println(httpMessageLine);
                            break;
                        }else{
                            continue;
                        }
                    }else{
                        httpMessage.add(httpMessageLine);
                        System.out.println(httpMessageLine);
                    }
                }
            }
            logger.info("接收Http结束");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return httpMessage;
    }

    /**
     * 在Get请求的情况下，获取参数，并将参数置入 MyHttpRequest 对象
     * @param requestPathAndParameter
     * @param myHttpRequest
     * @param logger
     * @return
     */
    private static String getRequestPathAndSetMyHttpRequest(String requestPathAndParameter, MyHttpRequest myHttpRequest, Logger logger) {
        String requestPath;
        //如果存在参数走 if ，不存在走 else
        if (requestPathAndParameter.indexOf("?") != -1) {
            requestPathAndParameter = requestPathAndParameter.substring(1);
            requestPath = requestPathAndParameter.split("\\?")[0];
            logger.info("本次请求路径是 ： " + requestPath);
            String parameterString = requestPathAndParameter.split("\\?")[1];
            String[] parameters = parameterString.split("&");
            for (int i = 0; i < parameters.length; i++) {
                myHttpRequest.getMap().put(parameters[i].split("=")[0], parameters[i].split("=")[1]);
            }
        } else {
            requestPath = requestPathAndParameter.substring(1);
            logger.info("本次请求路径是 ： " + requestPath);
        }
        return requestPath;
    }

    /**
     * 创建线程池
     * @return
     * @throws FileNotFoundException
     */
    private static ThreadPoolExecutor createThreadPoolExecutor() throws FileNotFoundException {
        //读取yml配置文件
        Yaml yaml = new Yaml();
        InputStream inputStream = new FileInputStream("src\\main\\resources\\config.yml");
        Map<String, Object> threadPoolMap = new HashMap<>();
        threadPoolMap = yaml.load(inputStream);
        Map<String, Map<String, Object>> poolMap = (Map<String, Map<String, Object>>) threadPoolMap.get("thread");
        //创建线程池
        return new ThreadPoolExecutor(
                (Integer) poolMap.get("pool").get("coreSize"),
                (Integer) poolMap.get("pool").get("maxSize"),
                (Integer) poolMap.get("pool").get("keepAliveTime"),
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>((Integer) poolMap.get("pool").get("workQueueSize")),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }

    /**
     * 启动阶段，找出Servlet
     * @param logger
     */
    private static void startupPhase(Logger logger) {
        //项目所在目录
        String projectPath = "src/main/java";
        //获取其File对象
        File file = new File(projectPath);
        //调用该方法用于获取 .java 后缀文件,并获取全类名
        getJavaFile(file);
        logger.info("找到Java文件 ：" + javaClasses.toString());
        //调用该方法获取 Servlet 类对象
        getServlet();
        logger.info("找到Servlet文件 ：" + servletMap.toString());
    }

    /**
     * 遍历文件，找出 .java 后缀文件
     * @param file
     */
    private static void getJavaFile(File file) {
        File[] files = file.listFiles();
        String filePathTemp;
        for (File subFile : files) {
            //若是目录，则递归打印该目录下的文件
            if (subFile.isDirectory())
                getJavaFile(subFile);
            if (subFile.isFile()) {
                filePathTemp = subFile.toString().split("\\\\java")[1];
                filePathTemp = filePathTemp.substring(1);
                if (filePathTemp.length() >= 5 && filePathTemp.substring(filePathTemp.length() - 5).equals(".java")) {
                    String fullClassName = filePathTemp.replace('\\', '.');
                    fullClassName = fullClassName.substring(0, filePathTemp.length() - 5);
                    javaClasses.add(fullClassName);
                }
            }
        }
    }

    /**
     * 在找到的 .Java 文件中，获取Servlet
     */
    private static void getServlet() {
        try {
            for (int i = 0; i < javaClasses.size(); i++) {
                String javaFilePath = javaClasses.get(i);
                Class<?> javaClass = null;
                javaClass = Class.forName(javaFilePath);
                if (javaClass.isAnnotationPresent(MyServlet.class)) {
                    servletMap.put(javaClass.getAnnotation(MyServlet.class).path(), javaClass);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}
